package be.rivendale.geometry;

import be.rivendale.mathematics.Ray;
import be.rivendale.mathematics.Triple;
import org.junit.Test;

import static be.rivendale.mathematics.MathematicalAssert.assertPointEquals;
import static be.rivendale.mathematics.MathematicalAssert.assertRealEquals;
import static org.junit.Assert.*;

public class AxisAlignedBoundingBoxTest {
	private static final AxisAlignedBoundingBox DEFAULT_AXIS_ALIGNED_BOUNDING_BOX = new AxisAlignedBoundingBox(new Triple(1, 2, 3), new Triple(2, 3, 4));

	@Test(expected = IllegalArgumentException.class)
	public void minimumBoundMustNotBeNull() {
		new AxisAlignedBoundingBox(null, new Triple(1, 2, 3));
	}

	@Test(expected = IllegalArgumentException.class)
	public void maximumBoundMustNotBeNull() {
		new AxisAlignedBoundingBox(new Triple(1, 2, 3), null);
	}
	
	/**
	 * A zero dimension axis aligned bounding box is allowed because it does not break any mathematical rules.
	 * Also this is convenient for calculating bounding boxes of objects that are entirely on one plane (as for a triangle for example).
	 */
	@Test(expected = IllegalArgumentException.class)
	public void maximumAndMinimumBoundsMayNotBeEqual() {
	    new AxisAlignedBoundingBox(new Triple(1, 1, 1), new Triple(1, 1, 1));
	}

	@Test(expected = IllegalArgumentException.class)
	public void maximumBoundMustNotBeLessThenTheMinimumBoundForTheXAxis() {
	    new AxisAlignedBoundingBox(new Triple(1, 1, 1), new Triple(0, 2, 2));
	}

	@Test(expected = IllegalArgumentException.class)
	public void maximumBoundMustNotBeLessThenTheMinimumBoundForTheYAxis() {
	    new AxisAlignedBoundingBox(new Triple(1, 1, 1), new Triple(2, 0, 2));
	}

	@Test(expected = IllegalArgumentException.class)
	public void maximumBoundMustNotBeLessThenTheMinimumBoundForTheZAxis() {
	    new AxisAlignedBoundingBox(new Triple(1, 1, 1), new Triple(2, 0, 2));
	}

	@Test
	public void minimumBoundCanBeRetrieved() {
		assertPointEquals(new Triple(1, 2, 3), DEFAULT_AXIS_ALIGNED_BOUNDING_BOX.getMinimumBound());
	}

	@Test
	public void maximumBoundCanBeRetrieved() {
		assertPointEquals(new Triple(2, 3, 4), DEFAULT_AXIS_ALIGNED_BOUNDING_BOX.getMaximumBound());
	}

	@Test
	public void intersectionReturnsNullIfNoIntersectionExists() {
		Ray ray = new Ray(new Triple(-1, 0, 0), new Triple(-3, -2, 5));
		assertNull(DEFAULT_AXIS_ALIGNED_BOUNDING_BOX.intersection(ray));
	}

	/**
	 * If the intersection occurs behind the origin (on the negative side of the ray)
	 */
	@Test
	public void intersectionReturnsNullIfTheIntersectionOccursBehindTheRayOrigin() {
		Ray ray = new Ray(new Triple(-1, 0, 0), new Triple(-0.3, 0.4, 0));
		assertNull(DEFAULT_AXIS_ALIGNED_BOUNDING_BOX.intersection(ray));
	}

	@Test
	public void intersectionReturnsTheValueOfTWhereTheRayEntersTheBox() {
		Ray ray = new Ray(new Triple(-3, -2, -5), new Triple(-0.3, 0.4, 0));
		assertRealEquals(1.666667, DEFAULT_AXIS_ALIGNED_BOUNDING_BOX.intersection(ray));
	}

	@Test
	public void widthCanBeRetrieved() {
		assertRealEquals(1, DEFAULT_AXIS_ALIGNED_BOUNDING_BOX.getWidth());
	}

	@Test
	public void heightCanBeRetrieved() {
	    assertRealEquals(1, DEFAULT_AXIS_ALIGNED_BOUNDING_BOX.getHeight());
	}

	@Test
	public void depthCanBeRetrieved() {
	    assertRealEquals(1, DEFAULT_AXIS_ALIGNED_BOUNDING_BOX.getDepth());
	}

	@Test
	public void intersectsWithAnotherAxisAlignedBoundingBoxReturnsTrueIfACornerOfOneBoxLiesWithinTheOther() {
	    AxisAlignedBoundingBox a = new AxisAlignedBoundingBox(new Triple(-1, 1, 0), new Triple(4, 7, 3));
		AxisAlignedBoundingBox b = new AxisAlignedBoundingBox(new Triple(3, 6, 2), new Triple(5, 8, 6));
		assertTrue(a.intersects(b));
	}

	@Test
	public void intersectsWithAnotherAxisAlignedBoundingBoxReturnsTrueNoCornersLieWitinTheOtherBox() {
		AxisAlignedBoundingBox a = new AxisAlignedBoundingBox(new Triple(0, 4, 3), new Triple(6, 9, 4));
	    AxisAlignedBoundingBox b = new AxisAlignedBoundingBox(new Triple(3, 6, 2), new Triple(5, 8, 6));
		assertTrue(a.intersects(b));
	}

	@Test
	public void intersectsWithAnotherAxisAlignedBoundingBoxReturnsFalseIfTheBoxesDoNotIntersectWithAnOverlappingXBound() {
	    AxisAlignedBoundingBox a = new AxisAlignedBoundingBox(new Triple(-1, 1, 0), new Triple(4, 5, 1));
		AxisAlignedBoundingBox b = new AxisAlignedBoundingBox(new Triple(3, 6, 2), new Triple(5, 8, 6));
		assertFalse(a.intersects(b));
	}

	@Test
	public void intersectsWithAnotherAxisAlignedBoundingBoxReturnsFalseIfTheBoxesDoNotIntersectWithAnOverlappingYBound() {
	    AxisAlignedBoundingBox a = new AxisAlignedBoundingBox(new Triple(-1, 1, 0), new Triple(2, 7, 1));
		AxisAlignedBoundingBox b = new AxisAlignedBoundingBox(new Triple(3, 6, 2), new Triple(5, 8, 6));
		assertFalse(a.intersects(b));
	}

	@Test
	public void intersectsWithAnotherAxisAlignedBoundingBoxReturnsFalseIfTheBoxesDoNotIntersectWithAnOverlappingZBound() {
	    AxisAlignedBoundingBox a = new AxisAlignedBoundingBox(new Triple(-1, 1, 0), new Triple(2, 5, 3));
		AxisAlignedBoundingBox b = new AxisAlignedBoundingBox(new Triple(3, 6, 2), new Triple(5, 8, 6));
		assertFalse(a.intersects(b));
	}

	@Test
	public void intersectsWithAnotherAxisAlignedBoundingBoxReturnsFalseIfTheBoxesDoNotIntersectWithNoOverlappingBounds() {
	    AxisAlignedBoundingBox a = new AxisAlignedBoundingBox(new Triple(-1, 1, 0), new Triple(2, 5, 1));
		AxisAlignedBoundingBox b = new AxisAlignedBoundingBox(new Triple(3, 6, 2), new Triple(5, 8, 6));
		assertFalse(a.intersects(b));
	}
}
